/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package prvydevw.manager;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.basex.core.BaseXException;
import org.basex.core.Context;
import org.basex.core.cmd.XQuery;
import prvydevw.entity.Subject;

/**
 *
 * @author Martin
 */
public class BasexSubjectManager implements SubjectManager
{

    private Context context;
    
    private static final Logger logger = Logger.getLogger(BasexSubjectManager.class.getName());
    private static final Object INSERT_LOCK = new Object();
    
    public BasexSubjectManager(Context context){
        this.context = context;
    }
    
    @Override
    public void createSubject(Subject subject)
    {
        validateSubject(subject);
        
        if (subject.getId() != null){
            throw new IllegalEntityException("Subject's ID is already set!");
        }
        
        try{
            synchronized(INSERT_LOCK) {
                Long subjectId = getNextId();
                String subjectXml = subjectToXml(subject, subjectId);
                String query = "insert node " + subjectXml + 
                        " into /prvydevw/subjects";
                new XQuery(query).execute(context);
                subject.setId(subjectId);
            }
        }
        catch(BaseXException ex){
            String msg = "Error when inserting subject into DB!";
            logger.log(Level.SEVERE, msg, ex);
            throw new ServiceFailureException(msg,ex);
        }
    }   

    @Override
    public void updateSubject(Subject subject)
    {
        validateSubject(subject);
        
        if (subject.getId() == null) {
            throw new IllegalEntityException("Error when updating, "
                    + "subject's ID is null");
        }
        
        try{
            String subjectXml = subjectToXml(subject,subject.getId());
            String query = "replace node /prvydevw/subjects/subject[@id = " +
                    subject.getId() + "] with " + subjectXml;
            
            new XQuery(query).execute(context);
        }
        catch (BaseXException ex) {
            String msg = "Error when updating Subject!";
            logger.log(Level.SEVERE, msg, ex);
            throw new ServiceFailureException(msg,ex);
        }
     }

    @Override
    public void deleteSubject(Subject subject)
    {
        validateSubject(subject);
        
        if (subject.getId() == null) {
            throw new IllegalEntityException("Error when deleting,"
                    + " subject's ID is null!");
        }
        
        try {
            String selectQuery = "for $subject in /prvydevw/subjects/subject"
                    + "[@id = "+ subject.getId() +"] return $subject/@id/data()";
            String subjectId = new XQuery(selectQuery).execute(context);
            
            if (subjectId.isEmpty()) {
                throw new ServiceFailureException("Error when deleting,"
                        + " subject: " + subject + " is not in DB!");
            }
            
            String deletePaymentsQuery = "delete node /prvydevw/payments/payment[subjectId/data() = "+subject.getId()+"]";
            
            new XQuery(deletePaymentsQuery).execute(context);
            
            String query = "delete node /prvydevw/subjects/subject[@id = "
                    + subject.getId() +"]";
            
            new XQuery(query).execute(context);
        }
        catch(BaseXException ex) {
            String msg = "Error when deleting subject!";
            logger.log(Level.SEVERE, msg, ex);
            throw new ServiceFailureException(msg,ex);
        }
    }

    @Override
    public Subject findSubject(Long id) {
        if (id == null){
            throw new IllegalArgumentException("ID of wanted subject is null!");
        }
        
        Subject subject = null;
        
        try {
            String subjectId = new XQuery("for $subject in /prvydevw/subjects/"
                    + "subject[@id = " + id + "] return $subject/@id/data()").execute(context);
            if (subjectId.isEmpty()){
                return null;
            }
            subject = getSubject(id);
        }
        catch (BaseXException ex) {
            String msg = "Error when getting subject";
            logger.log(Level.SEVERE, msg, ex);
            throw new ServiceFailureException(msg,ex);
        }
        
        return subject;
    }

    @Override
    public List<Subject> findAllSubjects()
    {
        List<Subject> allSubjects = new ArrayList<Subject>();
        
        try {
            String allSubjectsIDs = new XQuery("for $subject in /prvydevw/subjects/"
                    + "subject return $subject/@id/data()").execute(context);
            
            if (allSubjectsIDs.isEmpty()) {
                return allSubjects;
            }
            
            String[] subjectsIDs = allSubjectsIDs.split(" ");
            
            for (String id : subjectsIDs){
                allSubjects.add(getSubject(Long.valueOf(id)));
            }
            
        }
        catch (BaseXException ex){
            String msg = "Error when getting all subjects from DB!";
            logger.log(Level.SEVERE, msg, ex);
            throw new ServiceFailureException(msg,ex);
        }
        
        return allSubjects;
    }
    
    private static void validateSubject(Subject subject){
        if (subject == null) {
            throw new IllegalEntityException("Subject is null!");
        }
        
        if (subject.getName() == null) {
            throw new IllegalEntityException("Subject's name is not set!");
        }
        
        if (subject.getAccountNumber() == null) {
            throw new IllegalEntityException("Subject's account number is not set!");
        }
    }
    
    private Long getNextId() throws BaseXException
    {
        String newIdString = new XQuery("let $id := max(/prvydevw/subjects/subject/@id/data()) + 1 return $id").execute(context);
        
        if (newIdString.isEmpty()) {
            return 1l;
        }
        
        Long newId;
        
        try {
            newId = Long.parseLong(newIdString);
        }
        catch (NumberFormatException e) {
            throw new BaseXException("Unexpected data returned as id: " + newIdString, e);
        }
        
        return newId;
    }
    
    private static String subjectToXml(Subject subject, Long id){
        String subjectXml = 
                "<subject id=\"" + id + "\">" +
                "<name>" + subject.getName() + "</name>" +
                (subject.getRegId() != null ? "<regId>" + subject.getRegId() + "</regId>" : "") +
                "<accountNumber>" + subject.getAccountNumber() + "</accountNumber>" +
                "</subject>";
        
        return subjectXml;
    }
    
    private Subject getSubject(Long id) throws BaseXException {
        Subject subject = new Subject();
        
        String name = new XQuery("for $subject in /prvydevw/subjects/"
                    + "subject[@id="+id+"] return $subject/name/text()")
                    .execute(context);
        String regId = new XQuery("for $subject in /prvydevw/subjects/"
                    + "subject[@id="+id+"] return $subject/regId/text()")
                    .execute(context);
        String accountNumber = new XQuery("for $subject in /prvydevw/subjects/"
                    + "subject[@id="+id+"] return $subject/accountNumber/text()")
                    .execute(context);
        
        subject.setId(id);
        subject.setName(name);
        subject.setRegId(regId);
        subject.setAccountNumber(accountNumber);
        
        return subject;   
    }
    
}
